/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2020.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 31-3 */

/* strerror_tsd.c

   An implementation of strerror() that is made thread-safe through
   the use of thread-specific data.

   See also strerror_tls.c.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE                 /* Get '_sys_nerr' and '_sys_errlist'
									   declarations from <stdio.h> */
#endif

#include <stdio.h>
#include <string.h>                 /* Get declaration of strerror() */
#include <pthread.h>
#include "tlpi_hdr.h"

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;

#define MAX_ERROR_LEN 256    /* Maximum length of string in per-thread
							    buffer returned by strerror() */

static void                  /* Free thread-specific data buffer */
destructor(void *buf)                                                  // (1)
{
	free(buf);
}

static void                  /* One-time key creation function */
createKey(void)                                                        // (2)
{
	/* Allocate a unique thread-specific data key and save the address
	   of the destructor for thread-specific data buffers */

	int s = pthread_key_create(&strerrorKey, destructor);              // (3)
	if (s != 0)
		errExitEN(s, "pthread_key_create");
}

char *
strerror(int err)
{
	int s;
	char *buf;

	/* Make first caller allocate key for thread-specific data */

	s = pthread_once(&once, createKey);                                // (4)
	if (s != 0)
		errExitEN(s, "pthread_once");

	buf = (char*) pthread_getspecific(strerrorKey);                    // (5)
	if (buf == NULL) {  /* If first call from this thread, allocate
						   buffer for thread, and save its location */
		buf = (char*) malloc(MAX_ERROR_LEN);                           // (6)
		if (buf == NULL)
			errExit("malloc");

		s = pthread_setspecific(strerrorKey, buf);                     // (7)
		if (s != 0)
			errExitEN(s, "pthread_setspecific");
	}

	if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
		snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
	} else {
		strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
		buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
	}

	return buf;
}

void test_delete_tsd_key()
{
	int err = pthread_key_delete(strerrorKey);
	if (err)
		errExitEN(err, "pthread_key_delete");
}
